package be.nepherte.movmanager.client;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Set;
import com.google.gwt.user.client.rpc.IsSerializable;
import be.nepherte.movmanager.client.exceptions.InvalidLengthException;
import be.nepherte.movmanager.client.exceptions.InvalidRatingException;
import be.nepherte.movmanager.client.exceptions.InvalidTitleException;
import be.nepherte.movmanager.client.exceptions.InvalidDateException;

/**
 * This class represents a movie. Each movie must have:
 * <ul>
 * 		<li>a title</li>
 * </ul>
 * Optional properties are:
 * <ul>
 * 		<li>a list of directors</li>
 * 		<li>a list of writers</li>
 * 		<li>the release date</li>
 * 		<li>the duration of the movie</li>
 * 		<li>the actors and their corresponding role(s) in the movie</li>
 * 		<li>a rating</li>
 * 		<li>a summary</li>
 * 		<li>a list of genres</li>
 * 		<li>the studio that released the movie</li>
 * 		<li>whether you have already watched the movie or not</li>
 * 		<li>an URL to an image for this movie</li>
 * </ul>
 * <p>
 * This file is part of MovManager.
 * </p>
 * <p>
 * MovManager is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * </p>
 * <p>
 * MovManager is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * </p>
 * <p>
 * You should have received a copy of the GNU General Public License
 * along with MovManager.  If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses</a>.
 * </p>
 * @author	Bart Verhoeven
 * @since	23/05/2009
 * @version 0.1
 */
public class Movie implements IsSerializable {
	/**The title of this movie.*/
	private String title;
	/**A list with directors of this movie.*/
	private ArrayList<String> directors;
	/**A list with writers of this movie.*/
	private ArrayList<String> writers;
	/**The date this movie was released on.*/
	private Date releaseDate;
	/**The duration of this movie in minutes.*/
	private int duration;
	/**The rating of this movie.*/
	private double rating;
	/**The summary of this movie.*/
	private String summary;
	/**A list with genres of this movie.*/
	private ArrayList<String> genres;
	/**A mapping of the actors to their corresponding roles in this movie.*/
	private LinkedHashMap<String, ArrayList<String>> actorsAndRoles;
	/**The studio that released this movie.*/
	private String studio;
	/**Whether you have already watched this movie or not*/
	private boolean watched;
	/**The url to an image cover for this movie.*/
	private String image;
	
	/**
	 * Create a new movie. Only a title is required, everything else is optional. When one decides not to use an optional field, 
	 * take an empty string/list or 0. Never use null except for date!
	 * 
	 * @param	title
	 * 			the title of this movie
	 * @param	directors
	 * 			the director(s) of this movie
	 * @param	writers
	 * 			the writer(s) of this movie
	 * @param	date
	 * 			the date this movie was released on
	 * @param	length
	 * 			the duration of this movie in minutes
	 * @param	rating
	 * 			the rating of this movie
	 * @param	genres
	 * 			the genre(s) of this movie
	 * @param	summary
	 * 			the summary of this movie
	 * @param	actors
	 * 			the actors that play in this movie
	 * @param	actorsAndRoles
	 * 			a mapping of the actors and their corresponding roles in this movie. Key is the actor's name, value are his roles in this movie
	 * @param	studio
	 * 			the studio that released this movie
	 * @param	watched
	 * 			whether you have already watched the movie or not
	 * @param	image
	 * 			the url to an image cover for this movie
	 * @throws	InvalidTitleException
	 * 			a title is not valid when it is null or empty
	 * @throws	InvalidDateException
	 * 			a date is invalid when it is in the future
	 * @throws	InvalidLengthException
	 * 			a duration is invalid when it is a negative number
	 * @throws	InvalidRatingException
	 * 			a rating is invalid when it is not between 0 and 10 and doesn't have a fractional part of maximum 1 digit 
	 */
	public Movie(String title, ArrayList<String> directors, ArrayList<String> writers, Date date, int length, double rating, ArrayList<String> genres, String summary, LinkedHashMap<String, ArrayList<String>> actorsAndRoles, String studio, boolean watched, String image) throws InvalidTitleException, InvalidDateException, InvalidLengthException, InvalidRatingException {
		setTitle(title);
		setDirectors(directors);
		setWriters(writers);
		setReleaseDate(date);
		setLength(length);
		setRating(rating);
		setGenres(genres);
		setSummary(summary);
		setRoles(actorsAndRoles);
		setStudio(studio);
		setWatched(watched);
		setImage(image);
	}
	
	/**
	 * Empty, unused public constructor for Movie. This is required by GWT to make this object serializable.
	 */
	public Movie() {}

	/**
	 * Get the title of this movie. The returned result is never null or empty.
	 */
	public String getTitle() {
		return title;
	}
	
	/**
	 * Get a list with directors of this movie. The list is empty when there are no directors set for this movie. The returned result is never null.
	 */
	public ArrayList<String> getDirectors() {
		return directors;
	}
	
	/**
	 * Get a list with writers of this movie. The list is empty when there are no writers set for this movie. The returned result is never null.
	 */
	public ArrayList<String> getWriters() {
		return writers;
	}
	
	/**
	 * Get the date this movie was released on. Null if there is no date set for this movie.
	 */
	public Date getReleaseDate() {
		return releaseDate;
	}
	
	/**
	 * Get the duration of this movie in minutes. 0 means there's no length set for this movie. The returned result is never negative.
	 */
	public int getLength() {
		return duration;
	}
	
	/**
	 * Get the rating of this movie. The returned result is always a number between 0 and 10 with a maximum fractional part of 1 digit. 
	 * 0 means there's no rating set for this movie. Well, perhaps the movie just sucked. 10 is the highest possible score.
	 */
	public double getRating() {
		return rating;
	}
	
	/**
	 * Get the summary of this movie. An empty string is returned if there is no summary set for this movie. The returned result is never null.
	 */
	public String getSummary() {
		return summary;
	}
	
	/**
	 * Get a list with genres of this movie. An empty list is returned if there are no genres set for this movie. The returned result is never null.
	 */
	public ArrayList<String> getGenres() {
		return genres;
	}
	
	/**
	 * Get all the actors playing in this movie. An empty list is returned if there are no actors set for this movie. The returned result is never null.
	 */
	public Set<String> getActors() {
		return getActorsAndRoles().keySet();
	}
	
	
	/**
	 * Get all the roles in this movie. An empty list is returned if there are no roles set for this movie. The returned result is never null.
	 */
	private LinkedHashMap<String, ArrayList<String>> getActorsAndRoles() {
		return actorsAndRoles;
	}
	
	/**
	 * Get the roles the given actor plays in this movie. The returned result if null if the actor doesn't play in this movie.
	 * 
	 * @param	actor
	 * 			the actor you want to find the roles of
	 */
	public ArrayList<String> getRoles(String actor) {		
		return getActorsAndRoles().get(actor);		
	}
	
	/**
	 * Get the studio that released this movie. A empty string is returned if there is no studio set for this movie. The returned result is never null.
	 */
	public String getStudio() {
		return studio;
	}
	
	/**
	 * Get the url to the image cover for this movie. Use an empty string if you don't want to set an image for this movie, never use null!
	 * 
	 * @return	image
	 * 			the url to the image cover for this movie
	 */
	public String getImage() {
		return image;
	}
	
	/**
	 * Check whether one has already watched the movie or not.
	 */
	public boolean hasWatched() {
		return watched;
	}
	
	/**
	 * Set the title of this movie to the given title.
	 * 
	 * @param	title
	 * 			the new title for this movie
	 * @throws 	InvalidTitleException
	 * 			a title is not valid when it is null or empty
	 */
	public void setTitle(String title) throws InvalidTitleException {
		if (!isValidTitle(title))
			throw new InvalidTitleException();
		
		this.title = title;
	}
	
	/**
	 * Set the director(s) for this movie. Use an empty array list if you don't want to set directors for this movie, never use null!
	 * 
	 * @param	directors
	 * 			the new directors for this movie
	 */
	public void setDirectors(ArrayList<String> directors) {
		this.directors = directors;
	}
	
	/**
	 * Set the writer(s) for this movie. Use an empty array list if you don't want to set writers for this movie, never use null!
	 * 
	 * @param	writers
	 * 			the new writers for this movie
	 */
	public void setWriters(ArrayList<String> writers) {
		this.writers = writers;
	}
	
	/**
	 * Set the date this movie was released on. Use null if you don't want to set a date for this movie.
	 * 
	 * @param	year
	 * 			the new year for this movie
	 * @throws	InvalidDateException
	 * 			a date is invalid when it is in the future
	 */
	public void setReleaseDate(Date date) throws InvalidDateException {
		if (!isValidDate(date))
			throw new InvalidDateException();
		
		this.releaseDate = date;
	}
	
	/**
	 * Set the duration in minutes of this movie. Use 0 if you don't want to set a duration for this movie.
	 * 
	 * @param	duration
	 * 			the new duration of this movie in minutes
	 * @throws	InvalidLengthException
	 * 			a duration is invalid when it is a negative number
	 */
	public void setLength(int duration) throws InvalidLengthException {
		if (!isValidLength(duration))
			throw new InvalidLengthException();
		
		this.duration = duration;
	}
	
	/**
	 * Set the rating of this movie. Use 0 if you don't want to set a rating for this movie and 10 being the highest rating.
	 * 
	 * @param	rating
	 * 			the new rating of this movie
	 * @throws	InvalidRatingException
	 * 			a rating is invalid when it is not between 0 and 10 and doesn't have a fractional part of maximum 1 digit 
	 */
	public void setRating(double rating) throws InvalidRatingException {
		if (!isValidRating(rating))
			throw new InvalidRatingException();
		
		this.rating = rating;
	}
	
	/**
	 * Set the summary of this movie. Use an empty string if you don't want to set a summary for this movie, never use null!
	 * 
	 * @param	summary
	 * 			the summary of this movie
	 */
	public void setSummary(String summary) {
		this.summary = summary;
	}
	
	/**
	 * Set the genre(s) of this movie. Use an empty list if you don't want to set genres for this movie, never use null!
	 * 
	 * @param	genres
	 * 			the new genre(s) of this movie.
	 */
	public void setGenres(ArrayList<String> genres) {
		this.genres = genres;
	}
	
	/**
	 * Set the actors and the roles they play in this movie. Use an empty map if you don't want to set actors and their roles for
	 * this movie, never use null!
	 * 
	 * @param	roles
	 * 			the new actors and their corresponding roles in this movie. Key is the actor's name, value are his roles in this movie
	 */
	public void setRoles(LinkedHashMap<String, ArrayList<String>> roles) {
		this.actorsAndRoles = roles;
	}
	
	/**
	 * Set the studio that released this movie. Use an empty string if you don't want to set a studio for this movie, never use null!
	 * 
	 * @param	studio
	 * 			the new studio that released this movie
	 */
	public void setStudio(String studio) {
		this.studio = studio;
	}
	
	/**
	 * Set the url to the image cover for this movie. Use an empty string if you don't want to set a image cover for this movie, never use null!
	 * 
	 * @param	image
	 * 			the new url to the image cover for this movie
	 */
	public void setImage(String image) {
		this.image = image;
	}
	
	/**
	 * Set whether you have already watched this movie or not.
	 * 
	 * @param	watched
	 * 			whether you have already watched the movie or not
	 */
	public void setWatched(boolean watched) {
		this.watched = watched;
	}

	/**
	 * See if the given rating is valid.
	 * 
	 * @param	rating
	 * 			the rating you want to validate
	 * @return	true if rating is a number between 0 and 10 with a maximum fractional part of 1, false otherwise.
	 */
	private static boolean isValidRating(double rating) {
		return rating >= 0 && rating <= 10 && (Double.toString(rating).length() == 3 || rating == 10.0);
	}
	
	/**
	 * See if the given length is valid. A length is valid if it is not negative
	 * 
	 * @param	length
	 * 			the new length of this movie
	 */
	private static boolean isValidLength(int length) {
		return length >= 0;
	}
	
	/**
	 * 
	 * See if the given title is valid. A title is invalid if it is null or empty.
	 * 
	 * @param	title
	 * 			the title you want to validate
	 */
	private static boolean isValidTitle(String title) {
		return (title != null && !title.equals(""));
	}
	
	/**
	 * See if the given date is valid. A date is valid if it is in the past.
	 * 
	 * @param 	date
	 * 			the date you want to validate
	 */
	private static boolean isValidDate(Date date) {
		return  date == null || date.before(new Date());
	}
	
	/**
	 * Get a visually pleasing string representation of the directors of this movie. The format is director1, director2, director3, ...
	 */
	public String directorsToString() {
		String directorsString = "";
		
		for (int j=0; j < getDirectors().size(); j++) {
			directorsString += getDirectors().get(j);
			
			if (getDirectors().size() -1 != j && getDirectors().size() != 0)
				directorsString += ", ";
		}

		return directorsString;
	}
	
	/**
	 * Get a visually pleasing string representation of the writers of this movie. The format is writer1, writer2, writer3, ...
	 */
	public String writersToString() {
		String writersToString = "";
		
		for (int j=0; j < getWriters().size(); j++) {
			writersToString += getWriters().get(j);
			
			if (getWriters().size() -1 != j && getWriters().size() != 0)
				writersToString += ", ";
		}
		
		return writersToString;
	}
	
	/**
	 * Get a string representation of the genres of this movie. The format is genre1 | genre2 | genre3 | ...
	 */
	public String genresToString() {
		String genresString = "";
		
		for (int j=0; j < getGenres().size(); j++) {
			genresString += getGenres().get(j);
			
			if (getGenres().size() - 1 != j && getGenres().size() != 0)
				genresString += " | ";
		}
		
		return genresString;
	}
}
